from concurrent.futures import ThreadPoolExecutor, wait
from json.decoder import JSONDecodeError
from threading import Thread, Lock
import traceback
import time
import json
import os
DebugMode = True


# 用于给要被执行的函数打表记
def AutoRunMethod(num_of_run=-1, delay=60, atom=False, wait=False, test=False):
    def _(func):
        # 设置函数标记
        def AutoRunMethod_(*args, **kwargs):
            caller = traceback.extract_stack()[-2][2]
            if caller != 'task_thread' and not DebugMode:
                raise RuntimeError(f' AutoRunMethod "{func.__name__}" 不可被直接调用')
            if DebugMode:
                print(f"AutoTAsk {func.__name__} 开始执行")
            func(*args, **kwargs)
            if DebugMode:
                print(f"AutoTAsk {func.__name__} 执行完成")

        AutoRunMethod_._type = "once"
        AutoRunMethod_._wait = wait
        AutoRunMethod_._atom = atom
        AutoRunMethod_._test = test
        AutoRunMethod_._name = func.__name__
        if AutoRunMethod_._wait == True:
            AutoRunMethod_._atom = True
            AutoRunMethod_._num_of_run = 1
            AutoRunMethod_._delay = 0
        else:
            AutoRunMethod_._num_of_run = num_of_run
            AutoRunMethod_._delay = delay

        return AutoRunMethod_
    return _


def AutoRunMutiMethod(num_of_run=-1, delay=60, atom=False, wait=False, max_thread=10, test=False):
    def _(func):
        # 设置函数标记
        def AutoRunMethod_(*args, **kwargs):
            caller = traceback.extract_stack()[-2][2]
            if caller != 'muti_task_thread' and not DebugMode:
                raise RuntimeError(f' AutoRunMethod "{func.__name__}" 不可被直接调用')
            if DebugMode:
                print(f"AutoTAsk {func.__name__} 开始执行")
            yield from func(*args, **kwargs)
            if DebugMode:
                print(f"AutoTAsk {func.__name__} 执行完成")

        AutoRunMethod_._type = 'muti'
        AutoRunMethod_._wait = wait
        AutoRunMethod_._atom = atom
        AutoRunMethod_._test = test
        AutoRunMethod_._name = func.__name__
        AutoRunMethod_._threads = max_thread
        AutoRunMethod_._thread_list = []
        if AutoRunMethod_._wait == True:
            AutoRunMethod_._num_of_run = 1
            AutoRunMethod_._delay = 0
        else:
            AutoRunMethod_._num_of_run = num_of_run
            AutoRunMethod_._delay = delay

        return AutoRunMethod_
    return _


class AutoRunTask:
    def __init__(self, task_modules) -> None:
        self._auto_table = []
        self._config = {}
        self._load_conf()

        self._process_module(task_modules)  # 加载需要运行的

        self._closed = False  # 是否需要结束任务
        self._thread_list = []  # 包含协程事件循环的线程，防止阻塞主线程
        self._wait_list = []
        self._lock = Lock()

    def __del__(self):
        try:
            self.close()
        except Exception:
            pass

    def _load_conf(self):
        if not os.path.exists('./AutoTask.conf'):
            self._config = {}
            return
        with open("./AutoTask.conf", 'r') as f:
            try:
                self._config = json.loads(f.read())
            except JSONDecodeError:
                self._config = {}

    def close(self):
        self._closed = True
        for it in self._thread_list:
            while it.is_alive():
                time.sleep(0.1)
        with open("./AutoTask.conf", 'w') as f:
            f.write(json.dumps(self._config))
            f.close()

    def _process_module(self, modules):
        self._auto_table = []
        for module in modules:  # 枚举模块
            for it in module.__dict__:  # 模块里所有的变量包含在__dict__中
                if '__' in it:  # 隐藏变量不用管
                    continue
                value = module.__dict__[it]  # 获取某个属性
                if callable(value) and value.__name__ == 'AutoRunMethod_':
                    # 找到 Method
                    self._config.setdefault(value._name, 0)
                    if value._test:
                        self._config[value._name] = 0
                    self._auto_table.append(value)

    def run(self):
        def clear_thread():
            last_t = 0
            cycles = 60
            while not self._closed and len(self._thread_list) > 0:
                if time.time() - last_t < cycles:
                    time.sleep(1.)
                    continue

                last_t = time.time()

                self._lock.acquire()
                for t in self._thread_list:
                    if not t.is_alive():
                        self._thread_list.remove(t)
                self._lock.release()

        def get_task(method):
            def task_thread():
                cycles = method._delay
                last_nums = method._num_of_run
                atom = method._atom
                if self._config[method._name] != 0 and method._num_of_run == 1:
                    return
                while not self._closed and last_nums != 0:
                    if time.time() - self._config[method._name] < cycles:
                        time.sleep(min(cycles, 1.))
                        continue

                    last_nums -= 1
                    self._lock.acquire() if atom else None
                    try:
                        method()
                    except Exception as e:
                        raise e
                    finally:
                        self._lock.release() if atom else None

                    self._config[method._name] = time.time()
            return task_thread

        def get_muti_task(method):
            def muti_task_thread():
                cycles = method._delay
                last_nums = method._num_of_run
                atom = method._atom
                wait = method._wait
                max_thread = method._threads
                lock = Lock()
                if self._config[method._name] != 0 and method._num_of_run == 1:
                    return
                while not self._closed and last_nums != 0:
                    if time.time() - self._config[method._name] < cycles:
                        time.sleep(min(cycles, 1.))
                        continue
                    last_nums -= 1

                    arg_generater = method()
                    kernal = next(arg_generater)  # 获取要执行方法
                    pool = ThreadPoolExecutor(max_thread)

                    def sub_thread(*args):
                        if self._closed:
                            return
                        lock.acquire() if atom else None
                        try:
                            kernal(*args)
                        except Exception as e:
                            raise e
                        finally:
                            lock.release() if atom else None

                    tl = []
                    self._lock.acquire() if wait else None
                    for args in arg_generater:
                        tl.append(pool.submit(sub_thread, args))
                    pool.shutdown(wait=True)
                    self._lock.release() if wait else None
                    self._config[method._name] = time.time()
                for t in method._thread_list:
                    while t.is_alive():
                        pass
            return muti_task_thread

        for method in self._auto_table:
            if method._type == 'once':
                t = Thread(target=get_task(method))
            else:
                t = Thread(target=get_muti_task(method))
            t.daemon = True
            self._thread_list.append(t)
            if method._wait:
                self._wait_list.append(t)

        self._thread_list.append(Thread(target=clear_thread))
        self._thread_list[0].daemon = True
        for it in self._thread_list:
            it.start()
            try:
                for it in self._wait_list:
                    while it.is_alive():
                        time.sleep(0.1)
            except KeyboardInterrupt:
                return
